﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CoursePack;
using Heap;
using ScheduleGrid;
using System.Drawing;

namespace Scheduler
{
    public class ScheduleManager
    {
        #region Data
        private FlexHeap<Schedule> m_Schedules;
        private List<CourseChoiceInfo> m_CoursesNode;

        private ConstraintManager m_ConstraintManager;
        private Schedule m_CurrentSchedule;
        private Student m_Student;
        private DateTime m_StartTime;
        private DayView m_DayViewer;
        private int current = 0;
        private bool ALLOW_NO_CONFLICTS = true;
        private List<Color> m_Colors;
        #endregion

        public ScheduleManager(DateTime _StartTime,Student _Student)
        {
            this.m_StartTime = _StartTime;
            this.m_Student = _Student;
            this.m_CurrentSchedule = new Schedule(_StartTime, _Student,this);

            this.m_Colors = new List<Color>();
            this.m_CoursesNode = new List<CourseChoiceInfo>();
            this.m_ConstraintManager = new ConstraintManager();
        }

        public ScheduleManager(DateTime _StartTime, Student _Student, DayView dayViewComponent) 
            :this(_StartTime,_Student)
        {
            m_DayViewer = dayViewComponent;

            dayViewComponent.StartDate = DateTime.Now;
            dayViewComponent.ResolveAppointments += new ScheduleGrid.ResolveAppointmentsEventHandler(this.dayView1_ResolveAppointments);


     

        }


        /// <summary>
        /// Occurs when the DayViewer OnPaint methods is fired
        /// The procedure retreives all appointments to draw
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void dayView1_ResolveAppointments(object sender, ResolveAppointmentsEventArgs args)
        {
            List<Appointment> m_Apps = new List<Appointment>();

            foreach (Appointment m_App in this.m_CurrentSchedule.Appointments)
                if ((m_App.StartDate >= args.StartDate) &&
                    (m_App.StartDate <= args.EndDate))
                    m_Apps.Add(m_App);

            args.Appointments = m_Apps;
        }


        public bool AddConstraint(Constraint _Constraint)
        {
            return this.m_ConstraintManager.AddConstraint(_Constraint);
        }

        public bool RemoveTimeConstraint(TimeConstraint _Constraint)
        {
            return this.m_ConstraintManager.Constraints.Remove(_Constraint);
        }

        public bool RemoveAppointment(Appointment _App)
        {
            this.m_Colors.Add(_App.Color);
            return this.m_CurrentSchedule.RemoveAppointment(_App);
        }

        public bool AddCoursesNode(CourseChoiceInfo _Courses)
        {
            if (this.m_CoursesNode.Find(o => (o.Courses == _Courses.Courses))  == null)
            {
                this.m_CoursesNode.Add(_Courses);
                return true;
            }

            return false;
        }

        public bool RemoveCoursesNode(CourseChoiceInfo _Courses)
        {
            bool flag = this.m_CoursesNode.Remove(_Courses);

            foreach (Course c in _Courses.Courses)
                if (this.m_CurrentSchedule.Courses.Contains(c))
                    this.m_CurrentSchedule.RemoveCourse(c);


            this.DisplayAppointements();

            return flag;
        }

        private void GenerateColor()
        {
            this.m_Colors = new List<Color>();
            this.m_Colors.Add(Color.BlueViolet);
            this.m_Colors.Add(Color.YellowGreen);
            this.m_Colors.Add(Color.Tomato);
            this.m_Colors.Add(Color.Teal);
            this.m_Colors.Add(Color.Silver);
        }

        public void DisplayAppointements()
        {

            this.m_CurrentSchedule.Appointments.Clear();
            this.GenerateColor();

            DateTime input = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);

            int delta = DayOfWeek.Monday - input.DayOfWeek;
            DateTime monday = input.AddDays(delta);

            // Go through all the course and create appointement for it
            foreach (Course _Course in this.m_CurrentSchedule.Courses)
            {
                foreach (DayOfWeek day in _Course.Lecture.DaysOfTheWeek)
                {
                    DateTime start = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Lecture.StartDate.Hour, _Course.Lecture.StartDate.Minute, 0).AddDays((int)day - 1);
                    DateTime end = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Lecture.EndDate.Hour, _Course.Lecture.EndDate.Minute, 0).AddDays((int)day - 1);
                    Appointment Lecture = new AppointmentCourse( start, end, _Course);
                   
                    
                    Lecture.BorderColor = Color.Red;
                    Lecture.Color = this.m_Colors[this.m_Colors.Count - 1];

                    Lecture.Locked = true;
                    Lecture.Title = _Course.ToString() + "\n" + " LECTURE" + "  " + _Course.Lecture.Section + "\n" + _Course.Lecture.StartTime + " - " + _Course.Lecture.EndTime;

                    this.m_CurrentSchedule.Appointments.Add(Lecture);
                }


                if (_Course.HasTutorial)
                    foreach (DayOfWeek day in _Course.Tutorial.DaysOfTheWeek)
                    {
                        DateTime start = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Tutorial.StartDate.Hour, _Course.Tutorial.StartDate.Minute, 0).AddDays((int)day - 1);
                        DateTime end = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Tutorial.EndDate.Hour, _Course.Tutorial.EndDate.Minute, 0).AddDays((int)day - 1);
                        Appointment Tutorial = new AppointmentCourse( start, end,_Course);


                        Tutorial.BorderColor = Color.Blue;
                        Tutorial.Color = this.m_Colors[this.m_Colors.Count - 1];
                        Tutorial.Locked = true;
                        Tutorial.Title = _Course.ToString() + "\n" + " TUTORIAL" + "  " + _Course.Tutorial.Section + "\n" + _Course.Tutorial.StartTime + " - " + _Course.Tutorial.EndTime;

                        this.m_CurrentSchedule.Appointments.Add(Tutorial);
                    }


                if (_Course.HasLab)
                    foreach (DayOfWeek day in _Course.Lab.DaysOfTheWeek)
                    {
                        DateTime start = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Lab.StartDate.Hour, _Course.Lab.StartDate.Minute, 0).AddDays((int)day - 1);
                        DateTime end = new DateTime(monday.Year, monday.Month, monday.Day, _Course.Lab.EndDate.Hour, _Course.Lab.EndDate.Minute, 0).AddDays((int)day - 1);
                        Appointment Lab = new AppointmentCourse(start, end,_Course);

                        Lab.Locked = true;
                        Lab.BorderColor = Color.Green;
                        Lab.Color = this.m_Colors[this.m_Colors.Count - 1];
                        Lab.Title = _Course.ToString() + "\n" + " LAB" + "  " + _Course.Lab.Section + "\n" + _Course.Lab.StartTime + " - " + _Course.Lab.EndTime;

                        this.m_CurrentSchedule.Appointments.Add(Lab);
                    }
                this.m_Colors.RemoveAt(this.m_Colors.Count - 1);
            }




            foreach (TimeConstraint constraint in this.m_ConstraintManager.Constraints)
            {

                DateTime start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, constraint.StartTime.Hours, constraint.StartTime.Minutes, constraint.StartTime.Seconds);
                DateTime end = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, constraint.EndTime.Hours, constraint.EndTime.Minutes, constraint.EndTime.Seconds);


                int deltaa = constraint.DayOfWeek - start.DayOfWeek;

                start = start.AddDays(deltaa);
                end = end.AddDays(deltaa);

                AppointmentTimeConstraint m_App = new AppointmentTimeConstraint(start, end,constraint);

                m_App.BorderColor = Color.Red;
                m_App.Color = Color.Black;

                m_App.Locked = true;
                this.m_CurrentSchedule.Appointments.Add(m_App);
            }

            if (this.m_DayViewer != null)
                this.m_DayViewer.Invalidate();
        }


        public void Next()
        {
            if (current + 1 < this.m_Schedules.OrderedElements.Count)
            {
                this.m_CurrentSchedule = this.m_Schedules.OrderedElements[++current];
                this.DisplayAppointements();
            }
            else
                current = 0;

           
        }

        #region Generating Schedule Methods
        public void Generate()
        {
            // Cannot generate a schedule with 0 course
            if (this.m_CoursesNode.Count == 0)
                return;


            // All possible courses to generate from
            List<Course>[] bin = new List<Course>[this.m_CoursesNode.Count];

            // We go through ALL the courses in each Course Node added by the user
            for (int i = 0; i < this.m_CoursesNode.Count; i++)
            {
                bin[i] = new List<Course>();

                CourseChoiceInfo course = this.m_CoursesNode[i];

                // Does the user has a preference for the course teacher
                foreach (Course cc in course.Courses)
                {
                    if (course.TeacherPreference != "none")
                    {
                        //We add all the possible section with the specific teacher
                        if (cc.Lecture.Professor == course.TeacherPreference)
                        {
                            bin[i].Add(cc);
                        }
                    }
                    else
                    {
                        bin[i].Add(cc);
                    }
                }
            }

            Schedule schedule;

            //if there is only one course to generate a schedule
            FlexHeap<Schedule> HeapSchedule = new FlexHeap<Schedule>(1, 0);
            if (this.m_CoursesNode.Count == 1)
            {
                foreach (Course c in bin[0])
                {
                    schedule = new Schedule(this.m_StartTime, this.m_Student, this);

                    schedule.AddCourse(c);

                   
                    HeapSchedule.AddElement(this.m_ConstraintManager.EvaluateSchedule(schedule, 0), schedule);
                }

                this.m_CurrentSchedule = HeapSchedule.OrderedElements[0];
                this.m_Schedules = HeapSchedule;
            }
            // If there is 2 or more courses to generate a schedule
            else if (bin.Length >= 2)
            {
                List<List<Course>> AllPossibleSchedules = MergeTwoSets(bin[0], bin[1]);

                for (int x = 2; x < bin.Length; x++)
                {
                    AllPossibleSchedules = Merge1SetToList(AllPossibleSchedules, bin[x]);
                }


                // Create a Schedule Object with every possible schedule 
                foreach (List<Course> s in AllPossibleSchedules)
                {
                    schedule = new Schedule(this.m_StartTime, this.m_Student, this);

                    foreach (Course c in s)
                    {
                        schedule.AddCourse(c);
                    }


                    // We should not allow for conflicts in schedules
                    if (this.ALLOW_NO_CONFLICTS && schedule.NumberOfConflicts() == 0)
                    {
                        HeapSchedule.AddElement(this.m_ConstraintManager.EvaluateSchedule(schedule, 0), schedule);
                    }
                }

                this.m_CurrentSchedule = HeapSchedule.OrderedElements[0];
                this.m_Schedules = HeapSchedule;
            }
        }


        private List<List<Course>> MergeTwoSets(List<Course> set1, List<Course> set2)
        {
            List<List<Course>> tempTuples = new List<List<Course>>();

            for (int i = 0; i < set1.Count; i++)
            {
                for (int j = 0; j < set2.Count; j++)
                {
                    List<Course> tup = new List<Course>();
                    tup.Add(set1[i]);
                    tup.Add(set2[j]);

                    tempTuples.Add(tup);
                }
            }

            return tempTuples;
        }

        private List<List<Course>> Merge1SetToList(List<List<Course>> List, List<Course> set1)
        {

            List<List<Course>> tmp = new List<List<Course>>();
            List<Course> HeapSet = set1;

            for (int i = 0; i < List.Count; i++)
            {
                for (int j = 0; j < HeapSet.Count; j++)
                {
                    List<Course> tmpSub = new List<Course>();

                    foreach (Course c in List[i])
                    {
                        tmpSub.Add(c);
                    }

                    tmpSub.Add(HeapSet[j]);

                    tmp.Add(tmpSub);

                }
            }

            return tmp;
        }

        #endregion

        #region Accessors
        public Schedule CurrentSchedule
        {
            get { return this.m_CurrentSchedule; }
        }

        public ConstraintManager ConstraintManager
        {
            get { return this.m_ConstraintManager; }
        }

        public bool AllowNoConflicts
        {
            get
            {
                return this.ALLOW_NO_CONFLICTS;
            }
            set { this.ALLOW_NO_CONFLICTS = value; }
        }
        #endregion
    }
}
